//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// Trim objects inside/outside the mask rectangle.
//

# include "StdAfx.h"
# include "Resource.h"
# include "TrimMask.h"
# include "UndoRedo.h"
# include "Map.h"
# include "MapView.h"
# include "Position.h"
# include "Scale.h"
# include "Status.h"
# include "PtTrim.h"
# include "Globals.h"

class CTrimMaskAction : public CEditAction {
  public:
	CTrimMaskAction (const rect_t & _rectMask, bool _bInside) :
		m_rectMask (_rectMask),
		m_bInside (_bInside)
	{}

	virtual void GetName (string_t & _str) const {_str.append (m_bInside ? "trim inside" : "trim outside");}
	virtual cu_t CanUndo () const {return cuBackup;}
	virtual bool Do ();
	virtual void Undo () {}
	virtual void Redo () {}

  private:
	bool m_bInside;
	rect_t m_rectMask;
};

bool CTrimMaskAction::Do () {
	CCritSec::CLock lock (g_cs);

	const size_t cTotalRgns = g_map.GetRgnCount ();
	size_t cRgnsProcessed = 0;
	if (cTotalRgns == 0)
		return true;

	SetStatus (GetStr (IDS_STATUS_TRIMMING));
	SetProgressStatus (0);

	g_map.rectBound = rect_t ();

	for (size_t cKind = 0; cKind < rkMax; ++ cKind) {
		rgns_t & rgns = g_map.layers [cKind].rgns;

		for (rgns_t::iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
			rgn_t & rgn = * iRgn;
			if (rgn.IsDeleted ())
				continue;

			if (rgn.IsWTR ()) {
				rgn.RefreshAttributes (g_map, false);
				continue;
			}

			const bool bIntersects = m_rectMask.Intersects (rgn.rectBound);
			const bool bContains = m_rectMask.Contains (rgn.rectBound);

			if (bIntersects != bContains) {
				bool bModified = false;

				const size_t cElements = rgn.elements.size ();
				for (size_t cElement = 0; cElement < cElements; ++ cElement) {
					rgn_element_t & element = rgn.elements [cElement];
					if (element.bDeleted)
						continue;

					const bool bIntersects = m_rectMask.Intersects (element.rectBound);
					const bool bContains = m_rectMask.Contains (element.rectBound);
					if (bIntersects != bContains) {
						const bool bHasPointExtras = element.pExtra && ! element.pExtra->PointExtras.empty ();
						const bool bHasRGNodes = bHasPointExtras && HasRGNodes (element.pExtra->PointExtras);

						epoints_t epoints;
						Points2EPoints (element.points, bHasPointExtras ? & element.pExtra->PointExtras : NULL, epoints);

						polyepoints_t parts;
						if (cKind == rkPolygon)
							TrimPolygonOutsideRect  (m_rectMask, epoints, parts);
						else {
							assert (cKind == rkPolyline);
							TrimPolylineOutsideRect (m_rectMask, epoints, bHasRGNodes, parts, & rgn);
						}

						// NOTE: (& element) will be invalidated.
						const size_t cElementLevel = element.cLevel;
						const bool bElementHole    = element.bHole;

						element.bDeleted = true;

						for (polyepoints_t::iterator iPart = parts.begin (); iPart != parts.end (); ++ iPart) {
							const epoints_t & epoints = * iPart;
							const size_t cPoints = epoints.size ();
							if (cKind == rkPolygon ? cPoints < 3 : cPoints < 2)
								continue;

							points_t points;
							point_extras_t PointExtras;
							EPoints2Points (epoints, bHasPointExtras, points, PointExtras);

							// NOTE: this invalidates (& element).
							rgn.elements.push_back (rgn_element_t ());
							rgn_element_t & elementNew = rgn.elements.back ();

							elementNew.cFileOffset = -1;
							elementNew.cLevel      = cElementLevel;
							elementNew.bHole       = bElementHole;
							elementNew.points.swap (points);

							if (bHasPointExtras && ! PointExtras.empty ())
								elementNew.pExtra->PointExtras.swap (PointExtras);
						}

						bModified = true;
					} else if (bContains == m_bInside) {
						element.bDeleted = true;
						bModified = true;
					}
				}

				const size_t cOrigins = rgn.origins.size ();
				for (size_t cOrigin = 0; cOrigin < cOrigins; ++ cOrigin) {
					rgn_origin_t & origin = rgn.origins [cOrigin];
					if (origin.bDeleted)
						continue;

					if (m_rectMask.Contains (origin.point) == m_bInside) {
						origin.bDeleted = true;
						bModified = true;
					}
				}

				// TODO: delete empty objects!

				if (bModified) {
					rgn.dwFlags |= rgn_t::fModified;
					rgn.RefreshAttributes (g_map, false);
					if (cKind == rkPolygon)
						rgn.ReorderHoles ();
				} else
					g_map.rectBound.Extend (rgn.rectBound);
			} else if (bContains == m_bInside)
				rgn.Delete (g_map);
			else
				g_map.rectBound.Extend (rgn.rectBound);

			++ cRgnsProcessed;
			SetProgressStatus (cRgnsProcessed*100/cTotalRgns);
		}
	}

	g_map.bDirtyRGNodes = true;

	SetStatusReady ();

	return true;
}

///////////////////////////

void TrimMask (const rect_t & _rectMask, bool _bInside) {
	// Save visible rectangle.
	rect_t rectVisible;
	GetVisibleRect (rectVisible);

	PerformEditAction (new CTrimMaskAction (_rectMask, _bInside));

	// NOTE: update internal variables for new map bounding rectangle.
	RefreshScales ();

	// Restore visible rectangle.
	SetToCenter (point_t (rectVisible.MeanX (), rectVisible.MeanY ()));
	Refresh ();
}
